/////////////////////////////////////////////////////////////////
// Script:  starter script for cicp in second life
//          - starts cicp communication
//          - processes initial cicp data from hub (which are mainly to
//            create a cicp prim that acts as a gateway selector)
//          - dies when cicp hub has finished sending initial data
//
/////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////
// constants
/////////////////////////////////////////////////////////////////

integer CICP_PRIM_CHANNEL       = -28973762; // channel to for all
                                             // cicp prims (=atomic
                                             // weight of Phosporus)
integer CICP_BACK_CHANNEL       = -28973770; // channel for responses
                                             // to cicp init prim

string  CONFIG_FILE				= "producerConfig";
string  CONFIG_IGNORE_PREFIX    = "##"; // ignore lines starting with this (do not put "//" here, as it is problematic with URLs: "http://...")
/////////////////////////////////////////////////////////////////
// global vars
/////////////////////////////////////////////////////////////////

list    config                  = []; // contains variables out of
                                      // config file
key     configQuery             = NULL_KEY; // key of dataserverquery
                                            // to read config file
integer configQueryLine         = 0; // line that is actually read
                                     // from config file
list    httpParameters          = [ HTTP_METHOD, "GET",  // default HTTP parameters
                                    HTTP_MIMETYPE, "text/plain;charset=utf-8",
                                    HTTP_VERIFY_CERT, FALSE];
key     currentHttpRequestKey   = NULL_KEY; // key of last HTTP
                                            // request
integer	graphId					= -1; // graphid received by initial
                                     // response from producer (=layer id)
string  objectTitle             = "CICP Starter"; // displayed title above script
vector  infoTextColor           = <0., 1., 0.>; // color of infoText
float   infoTextAlpha           = 1.; // alpha transparency of infoText

/////////////////////////////////////////////////////////////////
// functions
/////////////////////////////////////////////////////////////////

// reads a configuration variable and returns its value (see slcicp_init script)
string getConfig(string varName)
{
    integer varPos = llListFindList(llList2ListStrided(config, 0, -1, 2), (list)varName);
    if (~varPos) {
        varPos = (varPos << 1) + 1;
        return llList2String(config, varPos);
    }
    else {
        error("Config variable \"" + varName + "\" not found.");
        return "";
    }
}

// handles errors
error(string message)
{
    llOwnerSay(message);
    info(message);
}

// displays informative messages (see slcicp_init: info())
info(string message)
{
    llSetText(objectTitle + "\n" + message, infoTextColor, infoTextAlpha);
}

// processes line from cicp hub
processDispatchLine(string line)
{
    if (llGetSubString(line, 0, 4) == "error") {
        // an error occured
        error("CICP Hub Error: " + line);
    }
    else if (line == "more") {
        // line has not ended yet, request more data
        cicpRequest("type=dispatch&layer=" + (string)graphId);
    }
    else if (line != "new" && line != "mod") {
        // data is supposed to be interpreted by a cicp prim
        list lineList = llParseString2List(line, [";"], []);
        // determine cicp prim's id
        integer targPrimId = llList2Integer(lineList, 0);
        integer targPrimChannel = getPrimChannel(targPrimId);
        // redirect command to prim
        llRegionSay(targPrimChannel, "cicp," + llList2String(lineList, 1));
    }
}

cicpRequest(string queryString)
{
    string url = getConfig("bridge_url");

    // add query string separator if necessary
    if (llSubStringIndex(url, "?") == -1) {
        url = url + "?";
    }
 
    url = url + queryString;

    // add protocol if necessary
    if (llGetSubString(url, 0, 3) != "http") {
        if ((integer)getConfig("ssl") == TRUE) {
            url = "https://" + url;
        }
        else {
            url = "http://" + url;
        }
    }

    string producerUrl = getConfig("producer_url");
    // add producer details
    if (getConfig("use_socket") == "1") {
        integer colonPos = llSubStringIndex(producerUrl, ":");
        string producerHost = llGetSubString(producerUrl, 0, colonPos - 1);
        string producerPort = llGetSubString(producerUrl, colonPos + 1, -1);
        url = url + "&prov_host=" + producerHost + "&prov_port=" + producerPort;
    }
    else {
        if (llGetSubString(producerUrl, 0, 3) != "http") {
            producerUrl = "http://" + producerUrl;
        }
        url = url + "&prov_url=" + producerUrl;
    }

    currentHttpRequestKey = llHTTPRequest(url, httpParameters, "");
}

integer getPrimChannel(integer targetPrimId)
{
    return CICP_PRIM_CHANNEL - 100 - targetPrimId;
}

/////////////////////////////////////////////////////////////////
//  states: default, waitingForTouch
/////////////////////////////////////////////////////////////////

 default {
     state_entry()
     {
         // listen on prim channel (mainly to receive config)
         llListen(CICP_PRIM_CHANNEL, "", NULL_KEY, "");

         // request config from slcicp
         llRegionSay(CICP_BACK_CHANNEL, "config");

         // display prim title
         info("Receiving adapter configuration... Please wait.");

         // set timeout for this action
         llSetTimerEvent(5.0);
     }

     on_rez(integer i)
     {
         llResetScript();
     }

     timer()
     {
         // action has timed out, no signal from slcicp received
         error("Error: No connection to CICP Adapter. Please rez first an adapter in this region and touch this prim when ready.");
         state resetOnTouch;
     }

     listen(integer channel, string name, key id, string msg)
     {
         list parameters = llCSV2List(msg);
         // process commands
         if (llList2String(parameters, 0) == "config") {
             config = llList2List(parameters, 1, -1);

             // save quick access variables
             httpParameters = llListReplaceList(httpParameters, [(integer)getConfig("ssl")], 5, 5);

             state loadConfig;
         }
     }

     state_exit()
     {
         llSetTimerEvent(0.0);
     }
 }

state resetOnTouch
{
    touch_start(integer n)
    {
        llResetScript();
    }
}

state waitingForTouch
// wait for user to start cicp by touching this prim
{
    state_entry()
    {
        // show some information for the user
        info("Move prim and touch it to start CICP.");
    }

    touch_start(integer n)
    {
        // display information about what we're doing
        info("Waiting for CICP hub response... Please wait.");

        // init cicp graph
        vector layerPos = llGetPos();
        string msg = "init," + (string)layerPos.x + "," + (string)layerPos.z + "," + (string)layerPos.y;

        cicpRequest("type=msg&msg=" + msg + "&region=" + llGetRegionName());
    }
    
    on_rez(integer i)
    {
        llResetScript();
    }

    http_response (key requestId, integer status, list metadata, string body)
    {
        if (requestId == currentHttpRequestKey) {
            // process cicp init instructions
            // split into lines
            list bodyList = llParseString2List(body, ["\n"], []);
            integer lineCount = llGetListLength(bodyList) - 1;
            integer i;
            string line = "";

            // extract layer/graph id from second line (second param
            // in CSV line)
            graphId = llList2Integer(llCSV2List(llList2String(bodyList, 1)), 1);

            // parse cicp instructions line per line
            // expected is to receive instructions for a cicp_prim
            // that then turns into a gateway selector that provides a
            // menu with available gateways to the user
            for (i = 0; i <= lineCount; i++) {
                line = llList2String(bodyList, i);

                processDispatchLine(line);
                if (i == lineCount && line != "more") {
                    // die since there seems to be no more data
                    // now it's the cicp prim's turn that has (hopefully)
                    // reacted on these commands
                    llDie();
                }
            }
        }
    }
}

state loadConfig
{
    state_entry()
    {
        info("Loading producer configuration...");
        // Check for config file existence
        if (llGetInventoryType(CONFIG_FILE) != INVENTORY_NONE) {
            // Start reading config notecard at line 0
            configQueryLine = 0;
            configQuery = llGetNotecardLine(CONFIG_FILE, configQueryLine);
        }
        else {
            info("No configuration (\"" + CONFIG_FILE + "\") found.");
            state resetOnTouch;
        }
    }

    dataserver(key qId, string data)
    {
        if (configQuery == qId) {
            // parse configuration line from configuration file
            if (data != EOF) {
                // parse config line
                data = llStringTrim(data, STRING_TRIM_HEAD);
                // check if there is data
                if (llStringLength(data) > 0) {
                    // check if line is a comment
                    string prefix = llGetSubString(data, 0, llStringLength(CONFIG_IGNORE_PREFIX) - 1);
                    if (prefix != CONFIG_IGNORE_PREFIX) {
                        // it is not, read first value as variable name and second as its value
                        integer whiteSpacePos = llSubStringIndex(data, " ");
                        if (~whiteSpacePos) {
                            // read name from line beginning to first whitespace
                            string varName = llGetSubString(data, 0, whiteSpacePos - 1);

                            // read value from whitespace until CONFIG_IGNORE_PREFIX or line end
                            string varValue = llGetSubString(data, whiteSpacePos+1, -1);

                            // check for comments at the end of line
                            integer commentPos = llSubStringIndex(varValue, CONFIG_IGNORE_PREFIX);
                            if (~commentPos) {
                                // cut away
                                varValue = llGetSubString(varValue, 0, commentPos - 1);
                            }

                            // remove whitespaces around value
                            varValue = llStringTrim(varValue, STRING_TRIM);
                            // parse varValue if needed
                            if (llToLower(varValue) == "true") {
                                varValue = "1";
                            }
                            else if (llToLower(varValue) == "false") {
                                varValue = "0";
                            }

                            // check if varName exists in configset and overwrite its value
                            integer configPos = llListFindList(llList2ListStrided(config, 0, -1, 2),
                                                               (list)varName);
                            if (~configPos) {
                                // move to value entry
                                configPos = (configPos << 1) + 1;
                                // overwrite config value
                                config = llListReplaceList(config, (list)varValue, configPos, configPos);
                            }
                            else {
                                // add new configuration variable to config
                                config += [varName, varValue];
                            }
                        }
                    }
                }
                ++configQueryLine;
                configQuery = llGetNotecardLine(CONFIG_FILE, configQueryLine);
            }
            else {
                objectTitle = objectTitle + " - " + getConfig("cicp_hub_name");
                // change to waitingForTouch state when finished
                state waitingForTouch;
            }
        }
    }
}
